Vue3 迁移指南

vue3.0 迁移指南

  1. 全局 api 更改为 应用程序实例.
    vue2.0 有很多全局的 api 和配置,比如 Vue.component 创建全局组件,Vue.directive 创建全局指令,Vue.mixins 和 Vue.use 等等..
    因为Vue2.0 通过 new Vue(…)来创建根 Vue 实例,从同一个 Vue 构造函数创建的根实例共享相同的全局配置.
    1
    2
    3
    4
    5
    6
    7
    // 这会影响两个根实例
    Vue.mixin({
    /* ... */
    })

    const app1 = new Vue({ el: '#app-1' })
    const app2 = new Vue({ el: '#app-2' })

vue3.0 提供了 一个全新的全局 API - creatApp,调用它返回一个应用实例.应用实例拥有当前全局 API 的子集.

1
2
3
4
import { createApp } from 'vue'
const app = createApp({})
// app.component,app.directive,app.mixin,app.use...
app.mount('#app');

  1. 全局和内部 api 以及重构为可 tree-shaking (删除无用代码,不打包到 bundle)
    vue2.0 时再用 Vue.nextTick() 或它的简单包装形式$nextTick()时,webpack 的 tree-shaking 不可摇动.
    Vue3.0 对全局和内部 api 进行了重构,考虑到 tree-shaking 的支持,全局 api 现在只能作为 es 模块侯建的命名导出进行访问.例如:
    1
    2
    import {nextTick} from 'vue'
    nextTick(...);

受影响的 api 有:

  • Vue.nextTick
  • VUe.observable(用 Vue.reactive 替代)
  • Vue.version
  • Vue.compile
  • Vue.set
  • Vue.delete
  1. v-model 用法更改
    在 vue2.0 中,v-model 用来双向绑定数据,但一个组件只能用于一个 v-model,如果需要多个双向绑定只能用.sync.
    1
    2
    3
    4
    5
    6
    // 2.x
    <ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
    // 简写
    <ChildComponent :title.sync="pageTitle" />
    // 子组件内部触发
    this.$emit('update:title', newValue)

在 vue3.0 中,v-model 通过后面要绑定的属性名来实现绑定多个值.
原理: vue2.0 的 v-model 通过绑定一个 value 属性和 input 事件,将输入e.target.value映射到 绑定的变量 值上.
而 vue3.0 相当于传递了modelValue 的 prop 并接收了抛出的 update 事件.

1
2
3
4
5
6
7
8
9
10
11
12
// 用在组件上
<custom-input v-model="searchText"></custom-input>
// 组件内部的 input 必须将属性绑定在 modelValue 上且 事件触发通过 update:modelValue 抛出.
app.component('custom-input', {
props: ['modelValue'],
template: `
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
>
`
})

  1. key 属性用法更改
    对于 v-if,v-else,v-else-if 的 key 不再必须,Vue3.0 会自动生成 唯一的key,不建议手动赋予 key 值.
    vue2.0 中 template 标签不能有 key 值,通常在它的子节点设置 key.在 Vue3.0 中,key 值应该设置在 template 标签中.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!-- Vue 2.x -->
    <template v-for="item in list">
    <div v-if="item.isVisible" :key="item.id">...</div>
    <span v-else :key="item.id">...</span>
    </template>

    <!-- Vue 3.x -->
    <template v-for="item in list" :key="item.id">
    <div v-if="item.isVisible">...</div>
    <span v-else>...</span>
    </template>
  2. v-if 和 v-for 优先级调整.
    vue2.0 中,v-for 的优先级最高.而在 Vue3.0 中,v-if 的优先级最高. 但都建议避免他们在同一元素上使用.

  3. v-for 中的 ref 数组.
    vue2.0 中 v-for 使用 ref 会用 ref 数组填充相应的 $refs.当 v-for 存在嵌套 v-for 时,这是不明确和效率低下的.
    Vue3.0 中.这样的用法将不再自动创建数组,需要将 ref 绑定到一个灵活地函数上.

  4. 只能使用普通函数创建功能组件(函数式组件).
    vue2.0 函数式组件示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // Vue 2 函数式组件示例
    export default {
    functional: true,
    props: ['level'],
    render(h, { props, data, children }) {
    return h(`h${props.level}`, data, children)
    }
    }
    // 或者
    <template functional>
    <component
    :is="`h${props.level}`"
    v-bind="attrs"
    v-on="listeners"
    />
    </template>

    <script>
    export default {
    props: ['level']
    }
    </script>

Vue3.0 不需要定义 functional,接收两个参数,props 和 context.(同 setup)

1
2
3
4
5
6
import { h } from 'vue'
const DynamicHeading = (props, context) => {
return h(`h${props.level}`, context.attrs, context.slots)
}
DynamicHeading.props = ['level']
export default DynamicHeading

  1. 异步组件
    Vue2.0 通过将组件定义为返回 promise 的函数来创建的.
    1
    const asyncPage = () => import('./NextPage.vue')

Vue3.0 由于函数式组件被定义为纯函数,因此异步组件需要包装在新的 defineAsyncComponent 方法显示定义.

1
2
import { defineAsyncComponent } from 'vue'
const asyncPage = defineAsyncComponent(() => import('./NextPage.vue'))

  1. h 渲染函数更改.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 2.x 语法 render 函数接收 h 之类的参数
    export default {
    render(h) {
    return h('div')
    }
    }
    // 3.x 语法 h 函数全局导入,不作为参数传递,可以用作 setup 的返回值函数
    import { h } from 'vue'

    export default {
    render() {
    return h('div')
    }
    }
  2. slot 统一
    在 3.x 中,将所有 this.$scopedSlots 替换为 this.$slots

  3. 自定义指令
    自定义指令的钩子函数更改为与组件声明周期统一的事件钩子.
    bind => beforeMount, inserted => mounted, 新增 beforeUpdate, update 与 componentUpdated => updated, 新增 beforeUnmounte, unbind => unmounted.

  4. watch 和 $watch 不再支持.分隔符字符串路径,请改为计算函数作为参数.

  5. destroyed 重命名为 unmounted, beforeDestroy 重命名为 beforeUnmount

  6. prop 默认值函数中不再能访问 this,可以把组件接收到的原始 prop 作为参数传递给默认函数.或使用 inject.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import { inject } from 'vue'

    export default {
    props: {
    theme: {
    default (props) {
    // `props` 是传递给组件的原始值。
    // 在任何类型/默认强制转换之前
    // 也可以使用 `inject` 来访问注入的 property
    return inject('theme', 'default-theme')
    }
    }
    }
    }
  7. data 组件选项不再接受纯 js Object,而必须是 function.而组件和 mixins 或 extends 基类合并是,现在将浅层次合并.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    const Mixin = {
    data() {
    return {
    user: {
    name: 'Jack',
    id: 1
    }
    }
    }
    }
    const CompA = {
    mixins: [Mixin],
    data() {
    return {
    user: {
    id: 2
    }
    }
    }
    }
    // vue 2.0 合并后是
    {
    user: {
    id: 2,
    name: 'Jack'
    }
    }
    // Vue 3.0 合并后是
    {
    user: {
    id: 2
    }
    }

迁移建议: 对于依赖 mixin 的深度合并行为的用户,我们建议重构代码以完全避免这种依赖,因为 mixin 的深度合并非常隐式,这让代码逻辑更难理解和调试。

  1. 过渡 class 名更改
    过渡类名 v-enter 修改为 v-enter-from、过渡类名 v-leave 修改为 v-leave-from。.

  2. 移除功能

  • 不再支持使用数字键吗作为 v-on 的修饰符
  • 不再支持 config.keyCodes

    1
    2
    3
    4
    5
    6
    7
    8
    Vue.config.keyCodes = {
    f1: 112
    }
    <!-- 键码版本 -->
    <input v-on:keyup.112="showHelpText" />

    <!-- Vue 3 在 v-on 上使用 按键修饰符, 建议对任何要用作修饰符的键使用 kebab-cased (短横线) 大小写名称 -->
    <input v-on:keyup.delete="confirmDelete" />
  • $on,$off 和 $once 实例方法(全局事件侦听器)已被移除,应用实例不再实现事件触发接口。
    $emit 仍然是现有 API 的一部分,因为它用于触发由父组件以声明方式附加的事件处理程序

  • Fileter 已删除,不再受支持,建议使用计算属性替代

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <p>{{ accountBalance | currencyUSD }}</p>

    export default{
    filters: {
    currencyUSD(value) {
    return '$' + value
    }
    }
    }
  • 删除 inline-template 内联属性

  • 删除$destroy 实例方法,用户不应再手动管理 Vue 组件的生命周期

vue3新特性

  1. setup
    setup是Vue3.0提供的一个新的属性,可以在setup中使用Composition API.setup函数有两个参数,分别是props和context。props 是组件外部传入进来的属性,contextcontext是一个对象,里面包含了三个属性attrs,slots,emit.
    attrs 与 vue2.0 的 this.$attrs 一样,是外部传入未在 props 中定义的属性.
    slots 对应 vue2.0 的 this.$slots 代表组件插槽.
    emit 对应 vue2.0 的 this.$emit,对外暴露的事件.
    setup 返回一个对象,对象中包含了组件使用到的 data 与一些函数或事件,也可以返回一个函数,对应 vue2.0 的 render 函数在里面可以使用 jsx.
    不要在 setup 中使用 this,通过 props 和 content 基本可以满足开发需求.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    export default {
    props: {
    value: {
    type: String,
    default: ""
    }
    },
    setup(props) {
    console.log(props.value)
    }
    }
  2. composition API
    在 vue2.0 中,我们在 data()函数中定义数据,在 methods,computed,watch 等等地方使用数据,书写逻辑.但随着功能增加,代码越来越难阅读和理解,因为现有的 api 迫使我们通过选项写代码,但有时候通过逻辑写代码更有意义.但 vue2.0 缺少一种简介的机制来提取和重用多个组件间的逻辑.

了解 composition api 前,想了解下 reactive 和 ref.

reactive

在 vue2.6 中有一个 新的 api, Vue.observer,通过这个 api 可以创建一个响应式对象.而 reactive 和 observer 功能基本一样.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div>{{ state.name }}</div>
</template>

<script>
import { reactive } from "vue";
export default {
setup() {
// 通过reactive声明一个可响应式的对象
const state = reactive({
name: "test"
});
setTimeout(() => {
state.name = "test123";
}, 1000 * 5);

return {
state
};
}
};
</script>

vue2.x 时,经常出现更改数据后页面没有刷新,需要使用 Vue.set()来解决.vue3.0 抛弃了 2.0 使用的 Object.defineProperty .使用 proxy 来监听.我们可以直接在reactive声明的对象上添加新的属性.
reactive 返回的不是原对象,而是 proxy 实例的一个全新对象.

ref

假如现在我们需要在一个函数里面声明用户的信息,那么我们可能会有两种不一样的写法

1
2
3
4
5
6
7
8
// 写法1
let name = 'vue'
let version = '3.0'
// 写法2
let info = {
name: 'vue',
version: '3.0'
}

对于写法1我们直接使用变量就可以了,而对于写法2,我们需要写成info.name的方式。我们可以发现info的写法与reactive是比较相似的,而Vue3.0也提供了另一种写法,就像写法1一样,即ref。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div>
<div>名称:{{ name }}</div>
</div>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
const name = ref("vue");
console.log('名称',name.value)
// 5秒后修改name为 react
setTimeout(() => {
name.value = "react";
}, 1000 * 5);
return {
name
};
}
};
</script>

reactive传入的是一个对象,返回的是一个响应式对象,而ref传入的是一个基本数据类型(其实引用类型也可以),返回的是传入值的响应式值.
reactive获取或修改属性可以直接通过state.xxx来操作,而ref返回值需要通过xxx.value的方式来修改或者读取数据。但是需要注意的是,在template中并不需要通过.value来获取值,这是因为template中已经做了解套。

  • toRefs会把一个响应式对象的每个属性都转换为一个ref,在 setup 返回值中…roRefs(data),在模板中引用不需要再加上 data 前缀,可以直接使用变量.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import { toRefs, reactive } from 'vue'
    export default {
    setup() {
    let data = reactive({
    count: 0
    })
    return {
    ...toRefs(data)
    }
    }
    }
  1. watch
    vue2.0 中使用 watch 来监听结果的变化
1
2
3
4
5
6
7
8
9
10
11
12
13
export default{
watch:{
name: {
handler(newVal,oldVal){
...
},
deep: true,
immediate: true
}
}
}
// 或使用
this.$watch('name',() => {...},{deep:true})

vue3.0 兼容 2.0 的写法,也提供了新的 api.分别是 watch 和 watchEffect.
watch 与2.0 $watch 用法一样.但可以监听单个值和函数的返回值,还可以监听多个数据源(放在一个数组中).
watchEffect会传入一个函数,然后立即执行这个函数,对函数中的响应式依赖进行监听,当依赖变化时,重新调用传入的函数.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { ref, watchEffect } from 'vue'
export default {
setup() {
const id = ref('0')
watchEffect(() => {
// 先输出 0 然后两秒后输出 1
console.log(id.value)
})

setTimeout(() => {
id.value = '1'
}, 2000)
}
}

vue2.0 中$watch 会返回一个函数用于停止监听.vue3.0 中 watch 和 watchEffect 也会返回一个用于 unwatch 的函数.

  1. computed
    vue 3.0 中 computed 与 vue2.0 一样.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // 2.0
    computed:{
    getName(){
    return this.firstName + this.lastName;
    }
    }
    // vue3.0
    const info = reactive({
    firstName: 'xx',
    lastName: 'xxx'
    })
    // getter
    const getName = computed(() => info.firstName + info.lastName)
    // 或 getter + setter
    const getName = computed({
    get: () => info.firstName + info.lastName,
    set(val){
    const name = val.split('-')
    info.firstName = name[0]
    info.lastName = name[1]
    }
    })
  2. readonly
    获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。只读代理是深层的:访问的任何嵌套 property 也是只读的。

  3. provide 和 inject
    provide 和 inject 启用依赖注入。只有在使用当前活动实例的 setup() 期间才能调用这两者。